/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.boot.mybatis.manager;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.iwindplus.boot.domain.annotation.TableFieldSafe;
import com.iwindplus.boot.domain.annotation.TableFieldSensitive;
import com.iwindplus.boot.domain.enums.AlgorithmTypeEnum;
import com.iwindplus.boot.domain.enums.BaseEnum;
import com.iwindplus.boot.mybatis.domain.property.MybatisProperty;
import com.iwindplus.boot.mybatis.domain.property.MybatisProperty.FieldConfig.CryptoConfig;
import com.iwindplus.boot.util.CryptoUtil;
import com.iwindplus.boot.util.FilesUtil;
import com.iwindplus.boot.util.SensitiveUtil;
import com.iwindplus.boot.util.domain.dto.SensitiveDTO;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

/**
 * mybatis字段管理器.
 *
 * @author zengdegui
 * @since 2025/04/22 00:46
 */
@Slf4j
@Getter
public class MybatisFieldManager {

    private MybatisPlusProperties mybatisPlusProperties;

    private MybatisProperty mybatisProperty;

    private static final Map<Class<?>, Set<Field>> FIELD_CACHE = new ConcurrentHashMap<>(16);

    /**
     * 构造方法.
     *
     * @param mybatisPlusProperties mybatisPlusProperties
     * @param mybatisProperty       mybatisProperty
     */
    public MybatisFieldManager(MybatisPlusProperties mybatisPlusProperties, MybatisProperty mybatisProperty) {
        this.mybatisProperty = mybatisProperty;
        this.mybatisPlusProperties = mybatisPlusProperties;
        this.scanClassesField(mybatisPlusProperties);
    }

    /**
     * 处理助手.
     *
     * @param source          原对象
     * @param inputEncrypt    是否启用输入加密
     * @param inputSensitive  是否启用输入脱敏
     * @param outputDecrypt   是否启用输出解密
     */
    public void processHandler(Object source, boolean inputEncrypt, boolean inputSensitive, boolean outputDecrypt) {
        if (ObjectUtil.isEmpty(source)) {
            return;
        }
        if (source instanceof Map<?, ?> map) {
            new HashSet<>(map.values()).forEach(obj -> this.processHandler(obj, inputEncrypt, inputSensitive, outputDecrypt));
            return;
        }
        if (source instanceof List<?> list) {
            if (ObjectUtil.isEmpty(list)) {
                return;
            }

            Object firstItem = list.get(0);
            if(ObjectUtil.isEmpty(firstItem) ) {
                return;
            }

            Set<Field> fieldList = this.getFieldList(firstItem.getClass());
            if (ObjectUtil.isEmpty(fieldList)) {
                return;
            }

            list.forEach(item -> this.processHandler(item, inputEncrypt, inputSensitive, outputDecrypt));
            return;
        }
        Set<Field> fieldList = this.getFieldList(source.getClass());
        if (ObjectUtil.isEmpty(fieldList)) {
            return;
        }
        MetaObject metaObject = SystemMetaObject.forObject(source);
        try {
            // 加密
            if (inputEncrypt) {
                this.encrypt(fieldList, metaObject);
            }

            // 解密
            if (outputDecrypt) {
                this.decrypt(fieldList, metaObject);
            }

            // 脱敏
            if (inputSensitive) {
                this.sensitive(fieldList, metaObject);
            }
        } catch (Exception ex) {
            log.error("处理加密/解密/脱敏字段时出错", ex);
        }
    }

    private void scanClassesField(MybatisPlusProperties mybatisPlusProperties) {
        final String packagePatterns = mybatisPlusProperties.getTypeAliasesPackage();
        final Class<?> assignableType = mybatisPlusProperties.getTypeAliasesSuperType();
        final Set<Class<?>> scanClasses = FilesUtil.scanClasses(packagePatterns, assignableType);
        if (ObjectUtil.isEmpty(scanClasses)) {
            return;
        }
        scanClasses.forEach(this::getFieldList);
    }

    private Set<Field> getFieldList(Class<?> clazz) {
        Set<Field> fieldList = FIELD_CACHE.get(clazz);
        if (ObjectUtil.isNotEmpty(fieldList)) {
            return fieldList;
        }
        fieldList = new HashSet<>(16);

        // 加解密注解
        List<Field> safeFieldList = this.getSafeFieldList(clazz);
        if (ObjectUtil.isNotEmpty(safeFieldList)) {
            fieldList.addAll(safeFieldList);
        }

        // 脱敏注解
        List<Field> sensitiveFieldList = this.getSensitiveFieldList(clazz);
        if (ObjectUtil.isNotEmpty(sensitiveFieldList)) {
            fieldList.addAll(sensitiveFieldList);
        }

        if (ObjectUtil.isNotEmpty(fieldList)) {
            FIELD_CACHE.put(clazz, fieldList);
        }

        return fieldList;
    }

    private List<Field> getSafeFieldList(Class<?> clazz) {
        List<Field> fieldList;
        if (clazz.isAnnotationPresent(TableFieldSafe.class)) {
            // 类上有注解，取出所有字段
            fieldList = FieldUtils.getAllFieldsList(clazz);
        } else {
            // 字段上有注解
            fieldList = FieldUtils.getFieldsListWithAnnotation(clazz, TableFieldSafe.class);
        }
        return fieldList;
    }

    private List<Field> getSensitiveFieldList(Class<?> clazz) {
        List<Field> fieldList;
        if (clazz.isAnnotationPresent(TableFieldSensitive.class)) {
            // 类上有注解，取出所有字段
            fieldList = FieldUtils.getAllFieldsList(clazz);
        } else {
            // 字段上有注解
            fieldList = FieldUtils.getFieldsListWithAnnotation(clazz, TableFieldSensitive.class);
        }
        return fieldList;
    }

    private void encrypt(Set<Field> fieldList, MetaObject metaObject) {
        final CryptoConfig crypto = this.mybatisProperty.getField().getCrypto();
        fieldList.forEach(field -> {
            final TableFieldSafe annotation = field.getAnnotation(TableFieldSafe.class);
            if (ObjectUtil.isNotEmpty(annotation) && annotation.inputEncrypt()) {
                this.buildAlgorithm(crypto, annotation);
                String name = field.getName();
                Object value = metaObject.getValue(name);
                if (ObjectUtil.isNotEmpty(value) && metaObject.getGetterType(name) == String.class) {
                    final String data = CryptoUtil.encrypt(Convert.toStr(value), crypto);
                    metaObject.setValue(name, data);
                }
            }
        });
    }

    private void decrypt(Set<Field> fieldList, MetaObject metaObject) {
        final CryptoConfig crypto = this.mybatisProperty.getField().getCrypto();
        fieldList.forEach(field -> {
            final TableFieldSafe annotation = field.getAnnotation(TableFieldSafe.class);
            if (ObjectUtil.isNotEmpty(annotation) && annotation.outputDecrypt()) {
                this.buildAlgorithm(crypto, annotation);
                String name = field.getName();
                Object value = metaObject.getValue(name);
                if (ObjectUtil.isNotEmpty(value) && metaObject.getGetterType(name) == String.class) {
                    final String data = CryptoUtil.decrypt(Convert.toStr(value), crypto);
                    metaObject.setValue(name, data);
                }
            }
        });
    }

    private void sensitive(Set<Field> fieldList, MetaObject metaObject) {
        fieldList.forEach(field -> {
            TableFieldSensitive annotation = field.getAnnotation(TableFieldSensitive.class);
            if (ObjectUtil.isNotEmpty(annotation) && annotation.enabled()) {
                SensitiveDTO sensitive = SensitiveDTO.builder()
                    .type(annotation.type())
                    .startInclude(annotation.startInclude())
                    .endReserve(annotation.endReserve())
                    .build();
                String name = field.getName();
                Object value = metaObject.getValue(name);
                if (ObjectUtil.isNotEmpty(value) && metaObject.getGetterType(name) == String.class) {
                    final String data = SensitiveUtil.desensitized(Convert.toStr(value), sensitive);
                    metaObject.setValue(name, data);
                }
            }
        });
    }

    private void buildAlgorithm(CryptoConfig crypto, TableFieldSafe annotation) {
        // 优先使用注解的算法
        final String algorithm = annotation.algorithm();
        if (ObjectUtil.isNotEmpty(algorithm)) {
            return;
        }
        final AlgorithmTypeEnum algorithmTypeEnum = BaseEnum.fromName(algorithm, AlgorithmTypeEnum.class);
        if (ObjectUtil.isEmpty(algorithmTypeEnum)) {
            return;
        }
        crypto.setAlgorithm(algorithmTypeEnum);
    }
}
